package com.server.multipartrequest;

import java.io.*;
import java.util.*;
import java.util.Map.Entry;

import javax.servlet.*;
import javax.servlet.http.*;

import com.oreilly.servlet.multipart.MultipartParser;
import com.oreilly.servlet.multipart.Part;
import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.ParamPart;
import com.oreilly.servlet.multipart.FileRenamePolicy;

public class MyMultipartRequest{

	  private static final int DEFAULT_MAX_POST_SIZE = 1024 * 1024;  // 1 Meg

	  protected Hashtable parameters = new Hashtable();  // name - Vector of values
	  protected Hashtable<String,MyUploadedFile> files = new Hashtable();       // name - UploadedFile

	  /**
	   * Constructs a new MultipartRequest to handle the specified request, 
	   * saving any uploaded files to the given directory, and limiting the 
	   * upload size to 1 Megabyte.  If the content is too large, an
	   * IOException is thrown.  This constructor actually parses the 
	   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	   * problem reading or parsing the request.
	   *
	   * @param request the servlet request.
	   * @param saveDirectory the directory in which to save any uploaded files.
	   * @exception IOException if the uploaded content is larger than 1 Megabyte
	   * or there's a problem reading or parsing the request.
	   */
	  public MyMultipartRequest(HttpServletRequest request,
	                          String saveDirectory) throws IOException {
	    this(request, saveDirectory, DEFAULT_MAX_POST_SIZE);
	  }

	  /**
	   * Constructs a new MultipartRequest to handle the specified request, 
	   * saving any uploaded files to the given directory, and limiting the 
	   * upload size to the specified length.  If the content is too large, an 
	   * IOException is thrown.  This constructor actually parses the 
	   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	   * problem reading or parsing the request.
	   *
	   * @param request the servlet request.
	   * @param saveDirectory the directory in which to save any uploaded files.
	   * @param maxPostSize the maximum size of the POST content.
	   * @exception IOException if the uploaded content is larger than 
	   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
	   */
	  public MyMultipartRequest(HttpServletRequest request,
	                          String saveDirectory,
	                          int maxPostSize) throws IOException {
	    this(request, saveDirectory, maxPostSize, null, null);
	  }

	  /**
	   * Constructs a new MultipartRequest to handle the specified request, 
	   * saving any uploaded files to the given directory, and limiting the 
	   * upload size to the specified length.  If the content is too large, an 
	   * IOException is thrown.  This constructor actually parses the 
	   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	   * problem reading or parsing the request.
	   *
	   * @param request the servlet request.
	   * @param saveDirectory the directory in which to save any uploaded files.
	   * @param encoding the encoding of the response, such as ISO-8859-1
	   * @exception IOException if the uploaded content is larger than 
	   * 1 Megabyte or there's a problem reading or parsing the request.
	   */
	  public MyMultipartRequest(HttpServletRequest request,
	                          String saveDirectory,
	                          String encoding) throws IOException {
	    this(request, saveDirectory, DEFAULT_MAX_POST_SIZE, encoding, null);
	  }

	  /**
	   * Constructs a new MultipartRequest to handle the specified request, 
	   * saving any uploaded files to the given directory, and limiting the 
	   * upload size to the specified length.  If the content is too large, an 
	   * IOException is thrown.  This constructor actually parses the 
	   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	   * problem reading or parsing the request.
	   *
	   * @param request the servlet request.
	   * @param saveDirectory the directory in which to save any uploaded files.
	   * @param maxPostSize the maximum size of the POST content.
	   * @param policy the rules for renaming in case of file name collisions
	   * @exception IOException if the uploaded content is larger than 
	   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
	   */
	  public MyMultipartRequest(HttpServletRequest request,
	                          String saveDirectory,
	                          int maxPostSize,
	                          FileRenamePolicy policy) throws IOException {
	    this(request, saveDirectory, maxPostSize, null, policy);
	  }

	  /**
	   * Constructs a new MultipartRequest to handle the specified request, 
	   * saving any uploaded files to the given directory, and limiting the 
	   * upload size to the specified length.  If the content is too large, an 
	   * IOException is thrown.  This constructor actually parses the 
	   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	   * problem reading or parsing the request.
	   *
	   * @param request the servlet request.
	   * @param saveDirectory the directory in which to save any uploaded files.
	   * @param maxPostSize the maximum size of the POST content.
	   * @param encoding the encoding of the response, such as ISO-8859-1
	   * @exception IOException if the uploaded content is larger than 
	   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
	   */
	  public MyMultipartRequest(HttpServletRequest request,
	                          String saveDirectory,
	                          int maxPostSize,
	                          String encoding) throws IOException {
	    this(request, saveDirectory, maxPostSize, encoding, null);
	  }

	  /**
	   * Constructs a new MultipartRequest to handle the specified request, 
	   * saving any uploaded files to the given directory, and limiting the 
	   * upload size to the specified length.  If the content is too large, an 
	   * IOException is thrown.  This constructor actually parses the 
	   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
	   * problem reading or parsing the request.
	   *
	   * To avoid file collisions, this constructor takes an implementation of the
	   * FileRenamePolicy interface to allow a pluggable rename policy.
	   *
	   * @param request the servlet request.
	   * @param saveDirectory the directory in which to save any uploaded files.
	   * @param maxPostSize the maximum size of the POST content.
	   * @param encoding the encoding of the response, such as ISO-8859-1
	   * @param policy a pluggable file rename policy
	   * @exception IOException if the uploaded content is larger than 
	   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
	   */
	  public MyMultipartRequest(HttpServletRequest request,
	                          String saveDirectory,
	                          int maxPostSize,
	                          String encoding,
	                          FileRenamePolicy policy) throws IOException {
	    // Sanity check values
	    if (request == null)
	      throw new IllegalArgumentException("request cannot be null");
	    if (saveDirectory == null)
	      throw new IllegalArgumentException("saveDirectory cannot be null");
	    if (maxPostSize <= 0) {
	      throw new IllegalArgumentException("maxPostSize must be positive");
	    }

	    // Save the dir
	    File dir = new File(saveDirectory);

	    // Check saveDirectory is truly a directory
	    if (!dir.isDirectory())
	      throw new IllegalArgumentException("Not a directory: " + saveDirectory);

	    // Check saveDirectory is writable
	    if (!dir.canWrite())
	      throw new IllegalArgumentException("Not writable: " + saveDirectory);

	    // Parse the incoming multipart, storing files in the dir provided, 
	    // and populate the meta objects which describe what we found
	    MultipartParser parser =
	      new MultipartParser(request, maxPostSize, true, true, encoding);

	    // Some people like to fetch query string parameters from
	    // MultipartRequest, so here we make that possible.  Thanks to 
	    // Ben Johnson, ben.johnson@merrillcorp.com, for the idea.
	    if (request.getQueryString() != null) {
	      // Let HttpUtils create a name->String[] structure
	      Hashtable queryParameters =
	        HttpUtils.parseQueryString(request.getQueryString());
	      // For our own use, name it a name->Vector structure
	      Enumeration queryParameterNames = queryParameters.keys();
	      while (queryParameterNames.hasMoreElements()) {
	        Object paramName = queryParameterNames.nextElement();
	        String[] values = (String[])queryParameters.get(paramName);
	        Vector newValues = new Vector();
	        for (int i = 0; i < values.length; i++) {
	          newValues.add(values[i]);
	        }
	        parameters.put(paramName, newValues);
	      }
	    }

	    Part part;
	    while ((part = parser.readNextPart()) != null) {
	      String name = part.getName();
	      if (name == null) {
	        throw new IOException(
	          "Malformed input: parameter name missing (known Opera 7 bug)");
	      }
	      if (part.isParam()) {
	        // It's a parameter part, add it to the vector of values
	        ParamPart paramPart = (ParamPart) part;
	        String value = paramPart.getStringValue();
	        Vector existingValues = (Vector)parameters.get(name);
	        if (existingValues == null) {
	          existingValues = new Vector();
	          parameters.put(name, existingValues);
	        }
	        existingValues.addElement(value);
	      }
	      else if (part.isFile()) {
	        // It's a file part
	        FilePart filePart = (FilePart) part;
	        String fileName = filePart.getFileName();
	        if (fileName != null) {
	          String newNameString = UUID.randomUUID().toString();
	          filePart.setRenamePolicy(new FileRenamePolicyUUID(newNameString));  // null policy is OK
	          // The part actually contained a file
	          filePart.writeTo(dir);
	          files.put(filePart.getFileName(), new MyUploadedFile(dir.toString(),
	                                           filePart.getFileName(),
	                                           fileName,
	                                           filePart.getContentType()));
	        }
	        else { 
	          // The field did not contain a file
	          files.put(fileName, new MyUploadedFile(null, null, null, null));
	        }
	      }
	    }
	  }

	  /**
	   * Constructor with an old signature, kept for backward compatibility.
	   * Without this constructor, a servlet compiled against a previous version 
	   * of this class (pre 1.4) would have to be recompiled to link with this 
	   * version.  This constructor supports the linking via the old signature.
	   * Callers must simply be careful to pass in an HttpServletRequest.
	   * 
	   */
	  public MyMultipartRequest(ServletRequest request,
	                          String saveDirectory) throws IOException {
	    this((HttpServletRequest)request, saveDirectory);
	  }

	  /**
	   * Constructor with an old signature, kept for backward compatibility.
	   * Without this constructor, a servlet compiled against a previous version 
	   * of this class (pre 1.4) would have to be recompiled to link with this 
	   * version.  This constructor supports the linking via the old signature.
	   * Callers must simply be careful to pass in an HttpServletRequest.
	   * 
	   */
	  public MyMultipartRequest(ServletRequest request,
	                          String saveDirectory,
	                          int maxPostSize) throws IOException {
	    this((HttpServletRequest)request, saveDirectory, maxPostSize);
	  }

	  /**
	   * Returns the names of all the parameters as an Enumeration of 
	   * Strings.  It returns an empty Enumeration if there are no parameters.
	   *
	   * @return the names of all the parameters as an Enumeration of Strings.
	   */
	  public Enumeration getParameterNames() {
	    return parameters.keys();
	  }

	  /**
	   * Returns the names of all the uploaded files as an Enumeration of 
	   * Strings.  It returns an empty Enumeration if there are no file input 
	   * fields on the form.  Any file input field that's left empty will result 
	   * in a FilePart with null contents.  Each file name is the name specified
	   * by the form, not by the user.
	   *
	   * @return the names of all the uploaded files as an Enumeration of Strings.
	   */
	  public Enumeration getFileNames() {
	    return files.keys();
	  }

	  /**
	   * Returns the value of the named parameter as a String, or null if 
	   * the parameter was not sent or was sent without a value.  The value 
	   * is guaranteed to be in its normal, decoded form.  If the parameter 
	   * has multiple values, only the last one is returned (for backward 
	   * compatibility).  For parameters with multiple values, it's possible
	   * the last "value" may be null.
	   *
	   * @param name the parameter name.
	   * @return the parameter value.
	   */
	  public String getParameter(String name) {
	    try {
	      Vector values = (Vector)parameters.get(name);
	      if (values == null || values.size() == 0) {
	        return null;
	      }
	      String value = (String)values.elementAt(values.size() - 1);
	      return value;
	    }
	    catch (Exception e) {
	      return null;
	    }
	  }

	  /**
	   * Returns the values of the named parameter as a String array, or null if 
	   * the parameter was not sent.  The array has one entry for each parameter 
	   * field sent.  If any field was sent without a value that entry is stored 
	   * in the array as a null.  The values are guaranteed to be in their 
	   * normal, decoded form.  A single value is returned as a one-element array.
	   *
	   * @param name the parameter name.
	   * @return the parameter values.
	   */
	  public String[] getParameterValues(String name) {
	    try {
	      Vector values = (Vector)parameters.get(name);
	      if (values == null || values.size() == 0) {
	        return null;
	      }
	      String[] valuesArray = new String[values.size()];
	      values.copyInto(valuesArray);
	      return valuesArray;
	    }
	    catch (Exception e) {
	      return null;
	    }
	  }

	  /**
	   * Returns the filesystem name of the specified file, or null if the 
	   * file was not included in the upload.  A filesystem name is the name 
	   * specified by the user.  It is also the name under which the file is 
	   * actually saved.
	   *
	   * @param name the html page's file parameter name.
	   * @return the filesystem name of the file.
	   */
	  public String getFilesystemName(String name) {
	    try {
	    	MyUploadedFile file = (MyUploadedFile)files.get(name);
	      return file.getFilesystemName();  // may be null
	    }
	    catch (Exception e) {
	      return null;
	    }
	  }

	  /**
	   * Returns the original filesystem name of the specified file (before any
	   * renaming policy was applied), or null if the file was not included in 
	   * the upload.  A filesystem name is the name specified by the user.
	   *
	   * @param name the html page's file parameter name.
	   * @return the original file name of the file.
	   */
	  public String getOriginalFileName(String name) {
	    try {
	    	MyUploadedFile file = (MyUploadedFile)files.get(name);
	      return file.getOriginalFileName();  // may be null
	    }
	    catch (Exception e) {
	      return null;
	    }
	  }

	  /**
	   * Returns the content type of the specified file (as supplied by the 
	   * client browser), or null if the file was not included in the upload.
	   *
	   * @param name the html page's file parameter name.
	   * @return the content type of the file.
	   */
	  public String getContentType(String name) {
	    try {
	    	MyUploadedFile file = (MyUploadedFile)files.get(name);
	      return file.getContentType();  // may be null
	    }
	    catch (Exception e) {
	      return null;
	    }
	  }

	  /**
	   * Returns a File object for the specified file saved on the server's 
	   * filesystem, or null if the file was not included in the upload.
	   *
	   * @param name the html page's file parameter name.
	   * @return a File object for the named file.
	   */
	  public File getFile(String name) {
	    try {
	    	MyUploadedFile file = (MyUploadedFile)files.get(name);
	      return file.getFile();  // may be null
	    }
	    catch (Exception e) {
	      return null;
	    }
	  }
	  
	  public Set<Entry<String, MyUploadedFile>> getFileInfo(){
		  return this.files.entrySet();
	  }

	  
	}


	// A class to hold information about an uploaded file.
	//
	class UploadedFile {

	  private String dir;
	  private String filename;
	  private String original;
	  private String type;

	  UploadedFile(String dir, String filename, String original, String type) {
	    this.dir = dir;
	    this.filename = filename;
	    this.original = original;
	    this.type = type;
	  }

	  public String getContentType() {
	    return type;
	  }

	  public String getFilesystemName() {
	    return filename;
	  }

	  public String getOriginalFileName() {
	    return original;
	  }

	  public File getFile() {
	    if (dir == null || filename == null) {
	      return null;
	    }
	    else {
	      return new File(dir + File.separator + filename);
	    }
	  }
	}

